﻿using System;
using System.Collections.Generic;
using Common;
using Exception = System.Exception;

namespace FSM
{
    internal sealed class Fsm<T> : FsmBase, IFsm<T>, IReference where T : class
    {
        /*
            当继承的抽象类与接口有同名方法时，有两种选择：
            隐式实现（统一行为）
            如果你希望这个类对这两个同名方法采用相同的实现逻辑，可以直接重写抽象方法，并让它也满足接口的要求

            显式实现（自定义行为），即显示的在实现的接口方法名前加上接口名
        */

        private T m_Owner;

        /// <summary>
        /// 状态字典
        /// </summary>
        private readonly Dictionary<Type, FsmState<T>> m_States;

        /// <summary>
        /// 数据字典
        /// </summary>
        private Dictionary<string, Variable> m_Datas;

        /// <summary>
        /// 当前状态
        /// </summary>
        private FsmState<T> m_CurrentState;

        /// <summary>
        /// 当前状态持续时间
        /// </summary>
        private float m_CurrentStateDurationTime;

        /// <summary>
        /// 是否已经被销毁了
        /// </summary>
        private bool m_IsDestroyed;

        public Fsm()
        {
            this.m_Owner = null;
            this.m_States = new Dictionary<Type, FsmState<T>>();
            this.m_Datas = new Dictionary<string, Variable>();
            this.m_CurrentState = null;
            this.m_CurrentStateDurationTime = 0.0f;
            this.m_IsDestroyed = false;
        }


        /// <summary>
        /// 获取有限状态机的持有者
        /// </summary>
        public T Owner => this.m_Owner;

        public override Type OwnerType => typeof(T);

        public override int FsmStateCount => this.m_States.Count;

        public override bool IsRunning => this.m_CurrentState != null;
        
        public override bool IsDestroyed => this.m_IsDestroyed;

        public FsmState<T> CurrentState => this.m_CurrentState;

        public override float CurrentStateDurationTime => this.m_CurrentStateDurationTime;
        public override string CurrentStateName => this.m_CurrentState?.GetType().FullName;

        /// <summary>
        /// 创建有限状态机
        /// </summary>
        /// <param name="name">有限状态机名称</param>
        /// <param name="owner">有限状态机持有者</param>
        /// <param name="states">有限状态机状态集合</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public static Fsm<T> Create(string name, T owner, params FsmState<T>[] states)
        {
            if ((object)owner == null)
                throw new Exception("FSM owner is invalid.");
            if (states == null || states.Length < 1)
                throw new Exception("FSM states is invalid.");
            Fsm<T> fsm = ReferencePool.Acquire<Fsm<T>>();
            fsm.Name = name;
            fsm.m_Owner = owner;
            fsm.m_IsDestroyed = false;
            foreach (FsmState<T> state in states)
            {
                // Type key = state != null ? state.GetType() : throw new Exception("Fsm states is invalid.");
                Type key = state?.GetType() ?? throw new Exception("Fsm states is invalid.");
                if (fsm.m_States.ContainsKey(key))
                    throw new Exception(string.Format("FSM '{0}' state '{1}' is already exist.",
                        new TypeNamePair(typeof(T), name), key.FullName));
                fsm.m_States.Add(key, state);
                state.OnInit((IFsm<T>)fsm);
            }

            return fsm;
        }

        /// <summary>
        /// 创建有限状态机
        /// </summary>
        /// <param name="name"></param>
        /// <param name="owner"></param>
        /// <param name="states"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public static Fsm<T> Create(string name, T owner, List<FsmState<T>> states)
        {
            if ((object)owner == null)
                throw new Exception("FSM owner is invalid.");
            if (states == null || states.Count < 1)
                throw new Exception("FSM states is invalid.");

            Fsm<T> fsm = ReferencePool.Acquire<Fsm<T>>();
            fsm.Name = name;
            fsm.m_Owner = owner;
            fsm.m_IsDestroyed = false;
            foreach (FsmState<T> state in states)
            {
                Type key = state?.GetType() ?? throw new Exception("Fsm states is invalid.");
                if (fsm.m_States.ContainsKey(key))
                    throw new Exception(string.Format("FSM '{0}' state '{1}' is already exist.",
                        new TypeNamePair(typeof(T), name), key.FullName));
                fsm.m_States.Add(key, state);
                state.OnInit((IFsm<T>)fsm);
            }

            return fsm;
        }

        public void Clear()
        {
            if(this.m_CurrentState != null)
                this.m_CurrentState.OnLeave((IFsm<T>) this, true);
            foreach(KeyValuePair<Type, FsmState<T>> state in this.m_States)
                state.Value.OnDestroy((IFsm<T>) this);
            this.Name = (string)null;
            this.m_Owner = default(T);
            this.m_States.Clear();
            if (this.m_Datas != null)
            {
                foreach (KeyValuePair<string, Variable> data in this.m_Datas)
                    if(data.Value != null)
                        ReferencePool.Release(data.Value);
                this.m_Datas.Clear();
            }

            this.m_CurrentState = (FsmState<T>)null;
            this.m_CurrentStateDurationTime = 0.0f;
            this.m_IsDestroyed = true;
        }


        public void Start<TState>() where TState : FsmState<T>
        {
            if (this.IsRunning)
                throw new Exception("FSM is running, can not start again.");
            FsmState<T> state = (FsmState<T>)this.GetState<TState>();
            if (state == null)
                throw new Exception(string.Format("FSM '{0}' can not start state '{1}' which is not exist.",
                    new TypeNamePair(typeof(T), this.Name), typeof(TState).FullName));
            this.m_CurrentStateDurationTime = 0.0f;
            this.m_CurrentState = state;
            this.m_CurrentState.OnEnter((IFsm<T>) this);
        }

        public void Start(Type stateType)
        {
            if (this.IsRunning)
                throw new Exception("FSM is Running, can not start again.");
            if (stateType == null)
                throw new Exception("State type is invalid.");
            FsmState<T> fsmState = typeof(FsmState<T>).IsAssignableFrom(stateType)
                ? this.GetState(stateType)
                : throw new Exception(string.Format("State type '{0}' is invalid.", stateType.FullName));
            
            this.m_CurrentState = fsmState ?? throw new Exception(string.Format("FSM '{0}' can not start state '{1}' which is not exist.",
                new TypeNamePair(typeof(T), this.Name), stateType.FullName));
            this.m_CurrentStateDurationTime = 0.0f;
            this.m_CurrentState.OnEnter((IFsm<T>)this);
        }
        

        public bool HasState<TState>() where TState : FsmState<T>
        {
            return this.m_States.ContainsKey(typeof(TState));
        }

        public bool HasState(Type stateType)
        {
            if (stateType == null)
                throw new Exception("State type is invalid.");
            return typeof(FsmState<T>).IsAssignableFrom(stateType) ? this.m_States.ContainsKey(stateType)
                : throw new Exception(string.Format("State type '{0}' is invalid.", stateType.FullName));
        }

        
        public TState GetState<TState>() where TState : FsmState<T>
        {
            FsmState<T> fsmState = (FsmState<T>)null;
            return this.m_States.TryGetValue(typeof(TState), out fsmState) ? (TState)fsmState : null;
        }
        
        public FsmState<T> GetState(Type stateType)
        {
            if(stateType == null)
                throw new Exception("State type is invalid.");
            if(!typeof(FsmState<T>).IsAssignableFrom(stateType))
                throw new Exception(string.Format("State type '{0}' is invalid.", stateType.FullName));
            return this.m_States.GetValueOrDefault(stateType);
        }
        
        public void GetAllStates(List<FsmState<T>> results)
        {
            if(results == null)
                throw new Exception("Results is invalid.");
            results.Clear();
            foreach (KeyValuePair<Type, FsmState<T>> state in this.m_States)
                results.Add(state.Value);
        }
        
        public FsmState<T>[] GetAllStates()
        {
            FsmState<T>[] array = new FsmState<T>[this.m_States.Count];
            this.m_States.Values.CopyTo(array, 0);
            return array;
        }

        public bool HasData(string name)
        {
            if (string.IsNullOrEmpty(name))
                throw new Exception("Data name is invalid");
            return this.m_Datas != null && this.m_Datas.ContainsKey(name);
        }
        
        public TData GetData<TData>(string name) where TData : Variable => (TData)this.m_Datas.GetValueOrDefault(name);
        
        public Variable GetData(string name)
        {
            if (string.IsNullOrEmpty(name))
                throw new Exception("Data name is invalid");
            return this.m_Datas?.GetValueOrDefault(name);
        }

        public void SetData<TData>(string name, TData data) where TData : Variable => SetData(name, (Variable)data);
        
        public void SetData(string name, Variable data)
        {
            if(string.IsNullOrEmpty( name))
                throw new Exception("Data name is invalid");
            if(this.m_Datas == null)
                this.m_Datas = new Dictionary<string, Variable>((IEqualityComparer<string>)StringComparer.Ordinal);
            var data1 = this.GetData(name);
            if(data1 != null)
                ReferencePool.Release(data1);
            this.m_Datas[name] = data;
        }


        public bool RemoveData(string name)
        {
            if (string.IsNullOrEmpty(name))
                throw new Exception("Data name is invalid.");
            if(this.m_Datas == null)
                return false;
            var data = this.GetData(name);
            if(data != null)
                ReferencePool.Release(data);
            return this.m_Datas.Remove(name);
        }
        /// <summary>
        /// 有限状态机轮询, 执行当前状态
        /// </summary>
        /// <param name="elapseSeconds"></param>
        /// <param name="realElapseSeconds"></param>
        internal override void Update(float elapseSeconds, float realElapseSeconds)
        {
            if (this.m_CurrentState == null)
                return;
            this.m_CurrentStateDurationTime += elapseSeconds;
            this.m_CurrentState.OnUpdate((IFsm<T>) this, elapseSeconds, realElapseSeconds);
        }

        internal override void Shutdown() => ReferencePool.Release((IReference)this);


        internal void ChangeState<TState>() where TState : FsmState<T> => ChangeState(typeof(TState));

        internal void ChangeState(Type stateType)
        {
            if(this.m_CurrentState == null)
                throw new Exception("Current state is invalid.");

            FsmState<T> state = this.GetState(stateType);
            if(state == null)
                throw new Exception(string.Format("FSM '{0}' can not change state to '{1}' which is not exist.",
                    new TypeNamePair(typeof(T), this.Name), stateType.FullName));
            this.m_CurrentState.OnLeave((IFsm<T>) this, false);
            this.m_CurrentStateDurationTime = 0.0f;
            this.m_CurrentState = state;
            this.m_CurrentState.OnEnter((IFsm<T>) this);
        }
    }
}